//! ThinkTime step implementation

use crate::error::{Error, Result};
use crate::models::{
    config::StepConfig,
    step::{IStep, StepType, StepResult, StepContext},
    Value, ValueMap,
};
use async_trait::async_trait;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
use tokio::time::sleep;
use uuid::Uuid;

/// Think time configuration types
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ThinkTimeType {
    /// Fixed duration
    Fixed,
    /// Random duration between min and max
    Random,
    /// Normal distribution with mean and std deviation
    Normal,
}

/// Think time configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThinkTimeConfig {
    /// Type of think time
    pub think_type: ThinkTimeType,

    /// Duration in seconds (for fixed type)
    pub duration: Option<f64>,

    /// Minimum duration in seconds (for random type)
    pub min_duration: Option<f64>,

    /// Maximum duration in seconds (for random type)
    pub max_duration: Option<f64>,

    /// Mean duration in seconds (for normal distribution)
    pub mean: Option<f64>,

    /// Standard deviation in seconds (for normal distribution)
    pub std_dev: Option<f64>,
}

/// ThinkTime step implementation
#[derive(Debug, Clone)]
pub struct ThinkTimeStep {
    id: Uuid,
    config: StepConfig,
    think_config: ThinkTimeConfig,
}

impl ThinkTimeStep {
    /// Create a new ThinkTime step
    pub fn new(config: StepConfig, think_config: ThinkTimeConfig) -> Self {
        Self {
            id: Uuid::new_v4(),
            config,
            think_config,
        }
    }

    /// Create a fixed duration think time step
    pub fn fixed(duration_seconds: f64) -> Self {
        let config = StepConfig {
            step_name: format!("Think Time ({}s)", duration_seconds),
            ..Default::default()
        };

        let think_config = ThinkTimeConfig {
            think_type: ThinkTimeType::Fixed,
            duration: Some(duration_seconds),
            min_duration: None,
            max_duration: None,
            mean: None,
            std_dev: None,
        };

        Self::new(config, think_config)
    }

    /// Create a random duration think time step
    pub fn random(min_seconds: f64, max_seconds: f64) -> Self {
        let config = StepConfig {
            step_name: format!("Think Time ({}-{}s)", min_seconds, max_seconds),
            ..Default::default()
        };

        let think_config = ThinkTimeConfig {
            think_type: ThinkTimeType::Random,
            duration: None,
            min_duration: Some(min_seconds),
            max_duration: Some(max_seconds),
            mean: None,
            std_dev: None,
        };

        Self::new(config, think_config)
    }

    /// Create a normal distribution think time step
    pub fn normal(mean_seconds: f64, std_dev_seconds: f64) -> Self {
        let config = StepConfig {
            step_name: format!("Think Time (μ={}s, σ={}s)", mean_seconds, std_dev_seconds),
            ..Default::default()
        };

        let think_config = ThinkTimeConfig {
            think_type: ThinkTimeType::Normal,
            duration: None,
            min_duration: None,
            max_duration: None,
            mean: Some(mean_seconds),
            std_dev: Some(std_dev_seconds),
        };

        Self::new(config, think_config)
    }

    /// Calculate the actual duration based on configuration
    fn calculate_duration(&self) -> Result<Duration> {
        let duration_seconds = match self.think_config.think_type {
            ThinkTimeType::Fixed => {
                self.think_config.duration
                    .ok_or_else(|| Error::Internal("Duration is required for fixed think time".to_string()))?
            }
            ThinkTimeType::Random => {
                let min = self.think_config.min_duration
                    .ok_or_else(|| Error::Internal("Min duration is required for random think time".to_string()))?;
                let max = self.think_config.max_duration
                    .ok_or_else(|| Error::Internal("Max duration is required for random think time".to_string()))?;

                if min > max {
                    return Err(Error::Internal("Min duration cannot be greater than max duration".to_string()));
                }

                let mut rng = rand::thread_rng();
                rng.gen_range(min..=max)
            }
            ThinkTimeType::Normal => {
                let mean = self.think_config.mean
                    .ok_or_else(|| Error::Internal("Mean is required for normal distribution think time".to_string()))?;
                let std_dev = self.think_config.std_dev
                    .ok_or_else(|| Error::Internal("Standard deviation is required for normal distribution think time".to_string()))?;

                // Simple Box-Muller transform for normal distribution
                let mut rng = rand::thread_rng();
                let u1: f64 = rng.gen();
                let u2: f64 = rng.gen();

                let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
                let duration = mean + std_dev * z0;

                // Ensure non-negative duration
                duration.max(0.0)
            }
        };

        // Convert to Duration, ensuring it's not negative
        if duration_seconds < 0.0 {
            return Err(Error::Internal("Think time duration cannot be negative".to_string()));
        }

        Ok(Duration::from_secs_f64(duration_seconds))
    }
}

#[async_trait]
impl IStep for ThinkTimeStep {
    fn name(&self) -> &str {
        &self.config.step_name
    }

    fn step_type(&self) -> StepType {
        StepType::ThinkTime
    }

    fn config(&self) -> &StepConfig {
        &self.config
    }

    fn config_mut(&mut self) -> &mut StepConfig {
        &mut self.config
    }

    async fn execute(&self, _context: &mut StepContext) -> Result<StepResult> {
        let start_time = Instant::now();
        let mut step_result = StepResult::new(self.id, self.name().to_string(), self.step_type());

        // Calculate the duration to wait
        let duration = self.calculate_duration()?;
        let duration_seconds = duration.as_secs_f64();

        // Perform the actual wait
        sleep(duration).await;

        // Record the results
        let mut response_data = ValueMap::new();
        response_data.insert("think_type".to_string(),
            Value::String(format!("{:?}", self.think_config.think_type).to_lowercase()));
        response_data.insert("planned_duration".to_string(),
            Value::Number(serde_json::Number::from_f64(duration_seconds).unwrap()));
        response_data.insert("actual_duration".to_string(),
            Value::Number(serde_json::Number::from_f64(start_time.elapsed().as_secs_f64()).unwrap()));

        // Add configuration details
        match self.think_config.think_type {
            ThinkTimeType::Fixed => {
                if let Some(duration) = self.think_config.duration {
                    response_data.insert("configured_duration".to_string(),
                        Value::Number(serde_json::Number::from_f64(duration).unwrap()));
                }
            }
            ThinkTimeType::Random => {
                if let Some(min) = self.think_config.min_duration {
                    response_data.insert("min_duration".to_string(),
                        Value::Number(serde_json::Number::from_f64(min).unwrap()));
                }
                if let Some(max) = self.think_config.max_duration {
                    response_data.insert("max_duration".to_string(),
                        Value::Number(serde_json::Number::from_f64(max).unwrap()));
                }
            }
            ThinkTimeType::Normal => {
                if let Some(mean) = self.think_config.mean {
                    response_data.insert("mean".to_string(),
                        Value::Number(serde_json::Number::from_f64(mean).unwrap()));
                }
                if let Some(std_dev) = self.think_config.std_dev {
                    response_data.insert("std_dev".to_string(),
                        Value::Number(serde_json::Number::from_f64(std_dev).unwrap()));
                }
            }
        }

        step_result.data = Some(Value::Object(response_data.into_iter().collect()));
        step_result.elapsed = start_time.elapsed().as_millis() as i64;
        step_result.success = true;

        Ok(step_result)
    }

    fn id(&self) -> Uuid {
        self.id
    }

    fn clone_step(&self) -> Box<dyn IStep> {
        Box::new(self.clone())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::time::Instant;

    #[tokio::test]
    async fn test_fixed_think_time() {
        use uuid::Uuid;

        let step = ThinkTimeStep::fixed(0.1); // 100ms
        let mut context = StepContext::new(Uuid::new_v4(), Uuid::new_v4());

        let start = Instant::now();
        let result = step.execute(&mut context).await.unwrap();
        let elapsed = start.elapsed();

        assert!(result.success);
        assert!(elapsed >= Duration::from_millis(90)); // Allow some tolerance
        assert!(elapsed <= Duration::from_millis(200)); // Allow some tolerance
    }

    #[tokio::test]
    async fn test_random_think_time() {
        use uuid::Uuid;

        let step = ThinkTimeStep::random(0.05, 0.15); // 50-150ms
        let mut context = StepContext::new(Uuid::new_v4(), Uuid::new_v4());

        let start = Instant::now();
        let result = step.execute(&mut context).await.unwrap();
        let elapsed = start.elapsed();

        assert!(result.success);
        assert!(elapsed >= Duration::from_millis(40)); // Allow some tolerance
        assert!(elapsed <= Duration::from_millis(200)); // Allow some tolerance
    }

    #[test]
    fn test_duration_calculation() {
        // Test fixed duration
        let fixed_step = ThinkTimeStep::fixed(1.5);
        let duration = fixed_step.calculate_duration().unwrap();
        assert_eq!(duration, Duration::from_secs_f64(1.5));

        // Test random duration bounds
        let random_step = ThinkTimeStep::random(1.0, 2.0);
        for _ in 0..10 {
            let duration = random_step.calculate_duration().unwrap();
            assert!(duration >= Duration::from_secs_f64(1.0));
            assert!(duration <= Duration::from_secs_f64(2.0));
        }
    }
}
